/***************************************************************************
 * IPPSESSION.CPP
 *
 *  OWebFolder: Implementation for CMSDAIPPSession (WebFolder Session)
 *
 *  Copyright (c)1999-2003; Microsoft Corporation. All rights reserved.
 *  Written by DSOI Office Integration, Microsoft Developer Support
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
 *  EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 ***************************************************************************/
#define DBINITCONSTANTS
#include "owebfolder.h"

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession
//
//  The CMSDAIPPSession object controls the basic web session for a web folder. In
//  this sample there is only one session allowed at a time. This object creates 
//  an instance of MSDAIPP, sets the bind properties reqested by the user, and then
//  makes the first rowset (wrapped in a CMSDAIPPRowset object) for the host.
//
//  All functions that require session level scope should happen in this class.
//
CMSDAIPPSession::CMSDAIPPSession(IDBInitialize* pdsoinit)
{
    ODS("New CMSDAIPPSession created.\n");
	m_cRef           = 1;
	m_pDataSource    = pdsoinit;
	m_pSession       = NULL;
	m_iServerType    = 0;
	m_iProvVersion   = 0;
	m_szProvVersion[0] = '\0';

	InternalGetDsoVersion();
}

CMSDAIPPSession::~CMSDAIPPSession(void)
{
    ODS("CMSDAIPPSession destroyed.\n");
	RELEASE_INTERFACE(m_pDataSource);
}


///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession::CreateMsdaippDso (static)
//
//  This routine creates the basic MSDAIPP DSO object. If this fails, it most likely
//  means that MSDAIPP is not installed or access has been denied. Either way, the 
//  sample can't run without it. If the method succeeds, it hands back a point to the
//  new CMSDAIPPSession object that wraps the DSO.
//  
STDMETHODIMP_(CMSDAIPPSession*) CMSDAIPPSession::CreateMsdaippDso()
{
	HRESULT      hr;
    ODS("Called CMSDAIPPSession::CreateMsdaippDso\n");
	IDBInitialize*   pinit = NULL;
	CMSDAIPPSession* pdso  = NULL;

	if (FAILED(hr = CoCreateInstance(CLSID_MSDAIPP_DSO, NULL, 
				CLSCTX_INPROC, IID_IDBInitialize, (void**)&pinit)))
	{
		TRACE1("CoCreateInstance for MSDAIPP failed! hr = 0x%X\n", hr);
	}
	else pdso = new CMSDAIPPSession(pinit);

	return pdso;
}

STDMETHODIMP_(ULONG) CMSDAIPPSession::Release(void)
{
	if (--m_cRef > 0) return m_cRef;
    ODS("CMSDAIPPSession in final release\n");
	InternalCloseSession();
	delete this;
	return 0;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession::CreateNewSession 
//
//  Caller should pass in a MSDAIPP_SESSIONINFO struct with parameters to bind to.
//  This function will set the properties, init the provider, and attempt to open
//  a session. Failure would likely occur if the URL specified did not exist, or 
//  did support the bind options specified.
//  
STDMETHODIMP CMSDAIPPSession::CreateNewSession(LP_MSDAIPP_SESSIONINFO props)
{
	HRESULT hr;

	ASSERT(props); CHECK_NULL_RETURN(props, E_POINTER);
    TRACE1("CMSDAIPPSession::CreateNewSession(URL == %s)\n", props->szDataSource);

 // Set all the properties passed in...
	hr = InternalSetProperties(props);
	if (SUCCEEDED(hr))
	{
	 // Then create the session object (binds to the web folder)...
		hr = InternalCreateSession();
	}

	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession::CreateRootRowset 
//
//  Once you have a session, you can set up the rowset. This function gets the
//  default rowset (i.e., the folder items at the location typed by the user) for
//  the URL bound to. We also get the URL and pass to the rowset, since it is possible
//  the web server redirected us, or the URL needed to be qualified.
//  
STDMETHODIMP CMSDAIPPSession::CreateRootRowset(CMSDAIPPRowset **ppMsdaippRowset)
{
	HRESULT         hr      = E_OUTOFMEMORY;
	CMSDAIPPRowset* prset   = NULL;
    BSTR            bstrURL = NULL;

	ASSERT(ppMsdaippRowset); CHECK_NULL_RETURN(ppMsdaippRowset, E_POINTER);
	ASSERT(m_pSession);      CHECK_NULL_RETURN(m_pSession,      E_ACCESSDENIED);

 // Make sure we have a URL for this bind session...
    bstrURL = GetDataSourceURL();
	if (bstrURL == NULL) LEAVE(hr, "GetDataSourceURL failed");

 // Create a rowset object using this URL and session...
	prset = CMSDAIPPRowset::CreateMsdaippRowset(bstrURL, this);
	if (prset == NULL) LEAVE(hr, "CreateMsdaippRowset failed");

 // Tell it to bind to the rowset (a NULL name means the default)...
	hr = prset->OpenRowset(NULL, NULL);
	if (FAILED(hr))
    {
        TRACE1("OpenRowset failed - 0x%X\r\n", hr);
        RELEASE_INTERFACE(prset);
    }

leave_routine:
    if (bstrURL) SysFreeString(bstrURL);
	*ppMsdaippRowset = prset;
	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession::GetDataSourceURL 
//
//  Ask the data source for the URL bound to.
//  
STDMETHODIMP_(BSTR) CMSDAIPPSession::GetDataSourceURL()
{
	HRESULT        hr;
	IDBProperties *pdbprops = NULL;
	DBPROPSET     *pPropSetData = NULL;
	DWORD          dwPropCnt, dwProp = DBPROP_INIT_DATASOURCE;
	DBPROPIDSET    rgPropIdSet;
	BSTR           bstrRet = NULL;

    ODS("CMSDAIPPSession::GetDataSourceURL\n");
	ASSERT(m_pSession); CHECK_NULL_RETURN(m_pSession, NULL);

	hr = m_pDataSource->QueryInterface(IID_IDBProperties, (void**)&pdbprops);
	LEAVE_ON_ERROR(hr, "QI for IDBProperties failed");

	rgPropIdSet.cPropertyIDs = 1;
	rgPropIdSet.guidPropertySet = DBPROPSET_DBINIT;
	rgPropIdSet.rgPropertyIDs = &dwProp;

	hr = pdbprops->GetProperties(1, &rgPropIdSet, &dwPropCnt, &pPropSetData);
	LEAVE_ON_ERROR(hr, "IDBProperties::GetProperties failed");

	if ((pPropSetData) && (pPropSetData->rgProperties))
	{
		if ((pPropSetData->rgProperties[0].dwStatus == DBSTATUS_S_OK) &&
			(pPropSetData->rgProperties[0].vValue.vt == VT_BSTR))
		{
			bstrRet = pPropSetData->rgProperties[0].vValue.bstrVal;
		}
		else VariantClear(&(pPropSetData->rgProperties[0].vValue));

		CoTaskMemFree(pPropSetData->rgProperties);
		CoTaskMemFree(pPropSetData);
    }
leave_routine:
	RELEASE_INTERFACE(pdbprops);
	return bstrRet;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession::InternalGetDsoVersion 
//
//  Internal function to get the version information for MSDAIPP. Although you can
//  directly query the DSO for the provider version, you cannot do so until after
//  the provider has been initialized. Since we want to know the version before that,
//  and since we also want the version string for display (debug), we'll just use
//  the file version of MSDAIPP.DLL itself.
//  
STDMETHODIMP CMSDAIPPSession::InternalGetDsoVersion()
{
	HRESULT  hr = E_FAIL;
	BYTE    *pDllVerInfo;
	DWORD    dwlen, dwinfo;
	CHAR     szFileName[MAX_PATH];

    ODS("CMSDAIPPSession::InternalGetDsoVersion\n");
	lstrcpy(m_szProvVersion, " MSDAIPP");

 // Get the module location from the loaded MSDAIPP.dll file...
	if (GetModuleFileName(GetModuleHandle("msdaipp.dll"), szFileName, MAX_PATH))
	{
	 // Use Win32 APIs to get the file version...
		dwlen = GetFileVersionInfoSize(szFileName, &dwinfo);
		if ((pDllVerInfo = new BYTE[(dwlen+1)]) && 
			GetFileVersionInfo(szFileName, 0, dwlen, pDllVerInfo))
		{
			LPVOID lpvi;
			if ((VerQueryValue(pDllVerInfo, "\\", &lpvi, (UINT*)&dwlen)) &&
				(dwlen >= sizeof(VS_FIXEDFILEINFO)))
			{
			 // All we care about is if this is version 2.0 (or higher). Lower versions
			 // can be treated the same...
				m_iProvVersion = 
					((HIWORD(((VS_FIXEDFILEINFO*)lpvi)->dwFileVersionMS) > 8) ? 2 : 1);

			 // We make the provider version string for display only...
				wsprintf(&m_szProvVersion[lstrlen(m_szProvVersion)],
					" %s (%hu.%hu.%hu.%hu)",
					((m_iProvVersion == 1) ? "1.5" : "2.0"),
					HIWORD(((VS_FIXEDFILEINFO*)lpvi)->dwFileVersionMS),
					LOWORD(((VS_FIXEDFILEINFO*)lpvi)->dwFileVersionMS),
					HIWORD(((VS_FIXEDFILEINFO*)lpvi)->dwFileVersionLS),
					LOWORD(((VS_FIXEDFILEINFO*)lpvi)->dwFileVersionLS));

				hr = S_OK;
			}
		}
		FREE_MEMPOINTER(pDllVerInfo);
	}

	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession::InternalSetProperties 
//
//  Internal function to set the DBProperties for the provider. All the settings
//  passed by the host in LP_MSDAIPP_SESSIONINFO are set here. We check the version
//  of the provider to know what can be set and what cannot. We'll only set those
//  properties recognized by the provider (errors will be treated as fatal).
//
//  Once the properties have been set, we initialize the provider. This should check
//  the server and validate the URL given. If this fails, the URL is incorrect.
//  
STDMETHODIMP CMSDAIPPSession::InternalSetProperties(LP_MSDAIPP_SESSIONINFO props)
{
	HRESULT  hr;
	IDBProperties* pdbprops = NULL;
	LPWSTR polestr;

    ODS("CMSDAIPPSession::InternalSetProperties\n");

    DBPROPSET    rgPropertySets[2];
	DBPROP       rgDBInitProps[7];
	DBPROP       rgMSDAIPPInitProps[9];

 // Set all the passed-in parameters into Unicode strings...
	BSTR  bstrDataSource = DsoConvertToBSTR(props->szDataSource);
	BSTR  bstrLockOwner  = DsoConvertToBSTR(props->szLockOwner);
    BSTR  bstrUserAgent  = DsoConvertToBSTR("OWebFolder MSDAIPP 2.0 Agent");
    BSTR  bstrCustomHdr  = DsoConvertToBSTR("OWF: OfficeWebFolder Host");
	BSTR  bstrClsid      = NULL;
	
 // Interestingly enough, the provider likes to have protocol GUID in string format...
	if (SUCCEEDED(StringFromCLSID(props->uidProtocolProvider, &polestr)))
	{
		bstrClsid = SysAllocString(polestr);
		CoTaskMemFree(polestr);
	}

 // Ask the DSO for IDBProperties. We will set 2 propsets (DBPROPSET_DBINIT and 
 // DBPROPSET_MSDAIPP_INIT) with some of the most commonly used properties. This
 // is largely overkill in a simple sample, but we wanted to show the types of 
 // properties that can be set and how...
	hr = m_pDataSource->QueryInterface(IID_IDBProperties, (void**)&pdbprops);
	LEAVE_ON_ERROR(hr, "QI for IDBProperties failed");

    memset(rgDBInitProps,      0, sizeof(7 * sizeof(DBPROP)));
    memset(rgMSDAIPPInitProps, 0, sizeof(9 * sizeof(DBPROP)));

	rgPropertySets[0].cProperties = 6;
    rgPropertySets[0].guidPropertySet = DBPROPSET_DBINIT;
    rgPropertySets[0].rgProperties = rgDBInitProps;

    rgPropertySets[0].rgProperties[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
    rgPropertySets[0].rgProperties[0].vValue.vt = VT_BSTR;
    rgPropertySets[0].rgProperties[0].vValue.bstrVal = bstrDataSource;

    rgPropertySets[0].rgProperties[1].dwPropertyID = DBPROP_INIT_MODE;
    rgPropertySets[0].rgProperties[1].vValue.vt = VT_I4;
    rgPropertySets[0].rgProperties[1].vValue.lVal = (props->grfBindFlags & (DBBINDURLFLAG_READ | DBBINDURLFLAG_WRITE | DBBINDURLFLAG_SHARE_DENY_READ | DBBINDURLFLAG_SHARE_DENY_WRITE));

    rgPropertySets[0].rgProperties[2].dwPropertyID = DBPROP_INIT_BINDFLAGS;
    rgPropertySets[0].rgProperties[2].vValue.vt = VT_I4;
    rgPropertySets[0].rgProperties[2].vValue.lVal = (props->grfBindFlags & DBBINDURLFLAG_OUTPUT);

    rgPropertySets[0].rgProperties[3].dwPropertyID = DBPROP_INIT_LOCKOWNER;
    rgPropertySets[0].rgProperties[3].vValue.vt = VT_BSTR;
    rgPropertySets[0].rgProperties[3].vValue.bstrVal = bstrLockOwner;

    rgPropertySets[0].rgProperties[4].dwPropertyID = DBPROP_INIT_LCID;
    rgPropertySets[0].rgProperties[4].vValue.vt = VT_I4;
    rgPropertySets[0].rgProperties[4].vValue.lVal = props->lcid;
	
    rgPropertySets[0].rgProperties[5].dwPropertyID = DBPROP_INIT_PROMPT;
    rgPropertySets[0].rgProperties[5].vValue.vt = VT_I4;
    rgPropertySets[0].rgProperties[5].vValue.lVal = DBPROMPT_COMPLETE;

    if ((GetMsdaippVersion() >= 2) && 
		(props->grfEncodingType != DBPROPVAL_UE_URL_PASSTHROUGH))
    {
		rgPropertySets[0].cProperties = 7;
		rgPropertySets[0].rgProperties[6].dwPropertyID = DBPROP_INIT_URLENCODING;
		rgPropertySets[0].rgProperties[6].vValue.vt = VT_I4;
		rgPropertySets[0].rgProperties[6].vValue.lVal = (props->grfEncodingType);
	}

    rgPropertySets[1].cProperties = 3;
    rgPropertySets[1].guidPropertySet = DBPROPSET_MSDAIPP_INIT;
    rgPropertySets[1].rgProperties = rgMSDAIPPInitProps;

    rgPropertySets[1].rgProperties[0].dwPropertyID = DBPROP_INIT_CACHEAGGRESSIVELY;
    rgPropertySets[1].rgProperties[0].vValue.vt = VT_BOOL;
    rgPropertySets[1].rgProperties[0].vValue.boolVal = (VARIANT_BOOL)
        ((props->grfProviderFlags & OWF_CACHEAGGRESSIVELY) ? VARIANT_TRUE : VARIANT_FALSE);

    rgPropertySets[1].rgProperties[1].dwPropertyID = DBPROP_INIT_TREATASOFFLINE;
    rgPropertySets[1].rgProperties[1].vValue.vt = VT_BOOL;
    rgPropertySets[1].rgProperties[1].vValue.boolVal = (VARIANT_BOOL)
        ((props->grfProviderFlags & OWF_CACHEAGGRESSIVELY) ? VARIANT_TRUE : VARIANT_FALSE);

    rgPropertySets[1].rgProperties[2].dwPropertyID = DBPROP_INIT_PROTOCOLPROVIDER;
    rgPropertySets[1].rgProperties[2].vValue.vt = VT_BSTR;
    rgPropertySets[1].rgProperties[2].vValue.bstrVal = bstrClsid;

    if (GetMsdaippVersion() >= 2)
    {
        rgPropertySets[1].cProperties = 7;

        rgPropertySets[1].rgProperties[3].dwPropertyID = DBPROP_INIT_CUSTOMHTTPHEADERS;
        rgPropertySets[1].rgProperties[3].vValue.vt = VT_BSTR;
        rgPropertySets[1].rgProperties[3].vValue.bstrVal = bstrCustomHdr;

        rgPropertySets[1].rgProperties[4].dwPropertyID = DBPROP_INIT_FLUSHWININETPWCACHE;
        rgPropertySets[1].rgProperties[4].vValue.vt = VT_BOOL;
        rgPropertySets[1].rgProperties[4].vValue.boolVal = (VARIANT_BOOL)
            ((props->grfProviderFlags & OWF_FLUSHWININETPWCACHE) ? VARIANT_TRUE : VARIANT_FALSE);

        rgPropertySets[1].rgProperties[5].dwPropertyID = DBPROP_INIT_USELOCKDISCOVERY;
        rgPropertySets[1].rgProperties[5].vValue.vt = VT_BOOL;
        rgPropertySets[1].rgProperties[5].vValue.boolVal = (VARIANT_BOOL)
            ((props->grfProviderFlags & OWF_DAV_USELOCKDISCOVERY) ? VARIANT_TRUE : VARIANT_FALSE);

        rgPropertySets[1].rgProperties[6].dwPropertyID = DBPROP_INIT_WEC_SHOWTHICKETFILES;
        rgPropertySets[1].rgProperties[6].vValue.vt = VT_BOOL;
        rgPropertySets[1].rgProperties[6].vValue.boolVal = (VARIANT_BOOL)
            ((props->grfProviderFlags & OWF_WEC_SHOWTHICKETFILES) ? VARIANT_TRUE : VARIANT_FALSE);
        
        if (props->grfProviderFlags & OWF_CUSTOMUSERAGENT)
        {
            rgPropertySets[1].cProperties = 8;
            rgPropertySets[1].rgProperties[7].dwPropertyID = DBPROP_INIT_USERAGENT;
            rgPropertySets[1].rgProperties[7].vValue.vt = VT_BSTR;
            rgPropertySets[1].rgProperties[7].vValue.bstrVal = bstrCustomHdr;
        }

    }

 // Now, we have the propsets setup, so make the call to set them...
    hr = pdbprops->SetProperties(2, rgPropertySets);
	LEAVE_ON_ERROR(hr, "IDBProps::SetProperties failed");
	if (hr != S_OK) // On the case of DB_S_ERRORSOCCURRED, we'll ignore (but trace out)...
	{
		ODS("DB_S_ERRORSOCCURRED -- SetProperties failed to set some!\n");
		// TODO: Report this back to the user at some time...
	}

 // Once we have the properties set, we can init the provider and bind to the 
 // data source given (i.e., to the URL)...
	hr = m_pDataSource->Initialize();
	LEAVE_ON_ERROR(hr, "IDBInitialize::Initialize failed");

leave_routine:
	SysFreeString(bstrClsid);
	SysFreeString(bstrLockOwner);
	SysFreeString(bstrDataSource);
	SysFreeString(bstrUserAgent);
	SysFreeString(bstrCustomHdr);

	RELEASE_INTERFACE(pdbprops);
	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession::InternalCreateSession 
//
//  If the provider has been initialized, we can now create the session object. This
//  session will be kept open until all sub folders are freed. If we are running with
//  version 2.0 of the provider, we can also get some extra information about the server
//  we are connected to (the DAV/WEC type).
//  
STDMETHODIMP CMSDAIPPSession::InternalCreateSession()
{
	HRESULT  hr;
	IDBCreateSession   *pdbcs = NULL;
	ISessionProperties *psesprops = NULL;
	DBPROPSET          *pPropSetSrvType = NULL;
	DWORD               dwPropCnt, dwPropSrvType;
	DBPROPIDSET         rgPropIdSet;

    ODS("CMSDAIPPSession::InternalCreateSession\n");

 // Ask to create a new session for the URL (and get the IOpenRowset interface
 // to use later on in getting the root rowset)...
	hr = m_pDataSource->QueryInterface(IID_IDBCreateSession, (void**)&pdbcs);
	LEAVE_ON_ERROR(hr, "QI for IDBCreateSession failed");

	hr = pdbcs->CreateSession(NULL, IID_IOpenRowset, (IUnknown**)&m_pSession);
	LEAVE_ON_ERROR(hr, "CreateSession failed");

 // If we are running the 2.0 provider, let's ask it the server type in order to
 // display that information to the user...
    m_iServerType = 0;
	if ((GetMsdaippVersion() > 1) && 
		(SUCCEEDED(m_pSession->QueryInterface(IID_ISessionProperties, (void**)&psesprops))))
	{
		rgPropIdSet.cPropertyIDs = 1;
		rgPropIdSet.guidPropertySet = DBPROPSET_MSDAIPP_SESSION;
		rgPropIdSet.rgPropertyIDs = &dwPropSrvType;
		dwPropSrvType = DBPROP_SESS_SERVERTYPE;

		if (SUCCEEDED(psesprops->GetProperties(1, &rgPropIdSet, &dwPropCnt, &pPropSetSrvType)) &&
			(pPropSetSrvType) && (pPropSetSrvType->rgProperties))
		{
			if ((pPropSetSrvType->rgProperties[0].dwStatus == DBSTATUS_S_OK) &&
				(pPropSetSrvType->rgProperties[0].vValue.vt == VT_I4))
			{
				m_iServerType = (UINT)(pPropSetSrvType->rgProperties[0].vValue.lVal);
			}

			CoTaskMemFree(pPropSetSrvType->rgProperties);
			CoTaskMemFree(pPropSetSrvType);
		}

		psesprops->Release();
	}

leave_routine:

	RELEASE_INTERFACE(pdbcs);
	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPSession::InternalCloseSession 
//
//  Close out the session and uninitialize the data source.
//  
STDMETHODIMP CMSDAIPPSession::InternalCloseSession()
{
	ODS("CMSDAIPPSession::InternalCloseSession\n");

	RELEASE_INTERFACE(m_pSession);

	if (m_pDataSource)
		m_pDataSource->Uninitialize();

	return S_OK;
}

